package com.gzhryc.pay.adaptation.weixin;

import com.gzhryc.common.StringTools;
import com.gzhryc.common.config.PropertyTools;
import com.gzhryc.common.json.JsonTools;
import com.gzhryc.common.logging.Logger;
import com.gzhryc.pay.adaptation.weixin.msg.PayerItem;
import com.gzhryc.pay.adaptation.weixin.msg.normal.*;
import com.gzhryc.pay.models.IPayOrder;
import com.gzhryc.pay.models.IRefundOrder;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 使用微信V3接口对接
 */
public class WXPayNormalPayService implements Serializable {

    private static final long serialVersionUID = 1L;

	static Logger log = Logger.getLogger(WXPayNormalPayService.class);

    String SCHEMA = "WECHATPAY2-SHA256-RSA2048";

    DefaultWXPayConfig config;
    P12File p12File;
    String privateKeyContent;

    public WXPayNormalPayService(){}

    public WXPayNormalPayService(Properties properties) {
        this.config = PropertyTools.build(properties).getObject(DefaultWXPayConfig.class);
        this.loadFiles();
    }

    public WXPayNormalPayService(DefaultWXPayConfig config) {
        this.config = config;
        this.loadFiles();
    }

    private void loadFiles(){
        if (StringUtils.isNotBlank(config.getCertificatePath())) {
            String path = config.getCertificatePath();
            if(path.endsWith("/")){
                path = path + config.getMchId() + "/apiclient_cert.p12";
            }else{
                path = path +"/" + config.getMchId() + "/apiclient_cert.p12";
            }
            File file = new File(path);
            if (file.exists()) {
                p12File = new P12File();
                p12File.setFile(file);
                p12File.setPassword(config.getCertificatePwd());
            }

            String keyPath = config.getCertificatePath();
            if(keyPath.endsWith("/")){
                keyPath = keyPath + config.getMchId()+ "/apiclient_key.pem";
            }else{
                keyPath = keyPath +"/" + config.getMchId() + "/apiclient_key.pem";
            }
            File keyFile = new File(keyPath);
            if(keyFile.exists()){
                try {
                    this.privateKeyContent = new String(Files.readAllBytes(Paths.get(keyPath)));
                } catch (IOException e) {
                    log.error(e.getMessage(),e);
                }
            }else{
                log.error("未加载微信APPID：" + config.getAppId() + "的证书文件，路径：" + keyPath);
            }
        }else {
            log.error("未配置微信APPID：" + config.getAppId() + "的证书文件");
        }
    }

    String getToken(String method, String url, String body)
            throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, UnsupportedEncodingException {
        String nonceStr = WXPayUtils.buildNonceStr();
        Long timestamp = WXPayUtils.buildTimestamp();
        String message = null;
        if(StringUtils.isBlank(body)) {
            message = method + "\n" + url + "\n" + timestamp + "\n" + nonceStr + "\n\n";
        }else{
            message = method + "\n" + url + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";
        }
        PrivateKey privateKey = WXPayUtils.readPrivateKey(privateKeyContent);
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(privateKey);
        sign.update(message.getBytes("utf-8"));
        String signature = Base64.getEncoder().encodeToString(sign.sign());

        return "mchid=\"" + this.config.getMchId() + "\"," + "nonce_str=\"" + nonceStr + "\"," + "timestamp=\"" + timestamp + "\"," + "serial_no=\""
                + this.config.getCertificateSerialNo() + "\"," + "signature=\"" + signature + "\"";
    }

    public WXPayNormalPayResponse applyPayOrder(IPayOrder payOrder) {
        CloseableHttpResponse response = null;
        try {
            CloseableHttpClient httpClient = WXHttpClient.getHttpClient(config.getAppId(),p12File);
            URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/v3/pay/transactions/jsapi").build();

            WXPayNormalPayRequest request = new WXPayNormalPayRequest();
            request.setAppid(config.getAppId());
            request.setMchid(config.getMchId());
            request.setDescription(payOrder.getTitle());
            request.setOut_trade_no(payOrder.getTradeNo());
            request.setNotify_url(this.config.getPayNotifyUrl());

            WXPayNormalPayRequest.AmountItem amountItem = new WXPayNormalPayRequest.AmountItem();
            amountItem.setTotal(payOrder.getPayMoney());
            request.setAmount(amountItem);

            PayerItem payerItem = new PayerItem();
            payerItem.setOpenid(payOrder.getPayerId());
            request.setPayer(payerItem);

            String content = JsonTools.toJson(request);
            StringEntity strEntity = new StringEntity(content, ContentType.create("application/json", Consts.UTF_8));

            HttpPost httpPost = new HttpPost(uri);
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Authorization", SCHEMA + " " + getToken("POST", uri.getPath(), content));
            httpPost.setEntity(strEntity);

            response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                String str = EntityUtils.toString(entity, Charset.forName("UTF-8"));
                log.info("微信创建普通订单响应：" + str);
                WXPayNormalPayResponse apiResponse = JsonTools.fromJson(str, WXPayNormalPayResponse.class);
                return apiResponse;
            }
        } catch (Exception e) {
            log.error("微信创建普通订单错误，"+ e.getMessage(), e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public WXPayNormalRefundResponse applyRefundOrder(IRefundOrder refundOrder) {
        CloseableHttpResponse response = null;
        try {
            CloseableHttpClient httpClient = WXHttpClient.getHttpClient(config.getAppId(),p12File);
            URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/v3/refund/domestic/refunds").build();

            WXPayNormalRefundRequest request = new WXPayNormalRefundRequest();
            if(StringTools.isNotBlank(refundOrder.getExternalPayTradeNo())) {
                request.setTransaction_id(refundOrder.getExternalPayTradeNo());
            }else {
                request.setOut_trade_no(refundOrder.getPayTradeNo());
            }
            request.setOut_refund_no(refundOrder.getTradeNo());
            request.setReason(refundOrder.getNote());
            request.setNotify_url(this.config.getRefundNotifyUrl());

            WXPayNormalRefundRequest.AmountItem amountItem = new WXPayNormalRefundRequest.AmountItem();
            amountItem.setRefund(refundOrder.getRefundMoney());
            amountItem.setTotal(refundOrder.getPayMoney());
            request.setAmount(amountItem);

            String content = JsonTools.toJson(request);
            StringEntity strEntity = new StringEntity(content, ContentType.create("application/json", Consts.UTF_8));

            HttpPost httpPost = new HttpPost(uri);
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Authorization", SCHEMA + " " + getToken("POST", uri.getPath(), content));
            httpPost.setEntity(strEntity);

            response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                String str = EntityUtils.toString(entity, Charset.forName("UTF-8"));
                log.info("微信普通订单退款响应：" + str);
                WXPayNormalRefundResponse apiResp = JsonTools.fromJson(str, WXPayNormalRefundResponse.class);
                return apiResp;
            }
        } catch (Exception e) {
            log.error("微信普通订单退款错误，"+ e.getMessage(), e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public boolean applyCloseOrder(String tradeNo) {
        CloseableHttpResponse response = null;
        try {
            CloseableHttpClient httpClient = WXHttpClient.getHttpClient(config.getAppId(),p12File);
            URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/v3/pay/transactions/out-trade-no/" + tradeNo + "/close").build();

            WXPayNormalCloseRequest request = new WXPayNormalCloseRequest();
            request.setMchid(config.getMchId());

            String content = JsonTools.toJson(request);
            StringEntity strEntity = new StringEntity(content, ContentType.create("application/json", Consts.UTF_8));

            HttpPost httpPost = new HttpPost(uri);
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Authorization", SCHEMA + " " + getToken("POST", uri.getPath(), content));
            httpPost.setEntity(strEntity);

            response = httpClient.execute(httpPost);
            if(response != null) {
                return true;
            }
        } catch (Exception e) {
            log.error("微信普通订单关闭错误，"+ e.getMessage(), e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    public WXPayNormalOrderResponse queryByTradeNo(String tradeNo) {
        CloseableHttpResponse response = null;
        try {
            URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com")
                        .setPath("/v3/pay/transactions/out-trade-no/" + tradeNo)
                        .setParameter("mchid",config.getMchId()).build();

            CloseableHttpClient httpClient = WXHttpClient.getHttpClient(config.getAppId(),p12File);

            HttpGet httpGet = new HttpGet(uri);
            httpGet.addHeader("Accept", "application/json");
            httpGet.addHeader("Authorization", SCHEMA + " " + getToken("GET", uri.getPath()+"?"+uri.getQuery(),null));

            response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                String str = EntityUtils.toString(entity, Charset.forName("UTF-8"));
                log.info("微信查询普通订单响应：" + str);
                WXPayNormalOrderResponse apiResponse = JsonTools.fromJson(str, WXPayNormalOrderResponse.class);
                return apiResponse;
            }
        } catch (Exception e) {
            log.error("微信查询普通订单错误，"+ e.getMessage(), e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public WXPayNormalOrderResponse queryByExternalTradeNo(String externalTradeNo) {
        CloseableHttpResponse response = null;
        try {
            URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com")
                        .setPath("/v3/pay/transactions/id/" + externalTradeNo)
                        .setParameter("mchid",config.getMchId()).build();

            CloseableHttpClient httpClient = WXHttpClient.getHttpClient(config.getAppId(),p12File);

            HttpGet httpGet = new HttpGet(uri);
            httpGet.addHeader("Accept", "application/json");
            httpGet.addHeader("Authorization", SCHEMA + " " + getToken("GET", uri.getPath()+"?"+uri.getQuery(),null));

            response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                String str = EntityUtils.toString(entity, Charset.forName("UTF-8"));
                log.info("微信查询普通订单响应：" + str);
                WXPayNormalOrderResponse apiResponse = JsonTools.fromJson(str, WXPayNormalOrderResponse.class);
                return apiResponse;
            }
        } catch (Exception e) {
            log.error("微信查询普通订单错误，"+ e.getMessage(), e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public WXPayNormalRefundOrderResponse queryRefundByTradeNo(String tradeNo) {
        CloseableHttpResponse response = null;
        try {
            URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com")
                    .setPath("/v3/refund/domestic/refunds/" + tradeNo)
                    .setParameter("mchid",config.getMchId()).build();

            CloseableHttpClient httpClient = WXHttpClient.getHttpClient(config.getAppId(),p12File);

            HttpGet httpGet = new HttpGet(uri);
            httpGet.addHeader("Accept", "application/json");
            httpGet.addHeader("Authorization", SCHEMA + " " + getToken("GET", uri.getPath()+"?"+uri.getQuery(),null));

            response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                String str = EntityUtils.toString(entity, Charset.forName("UTF-8"));
                log.info("微信查询普通退款订单响应：" + str);
                WXPayNormalRefundOrderResponse apiResponse = JsonTools.fromJson(str, WXPayNormalRefundOrderResponse.class);
                return apiResponse;
            }
        } catch (Exception e) {
            log.error("微信查询普通退款订单错误，"+ e.getMessage(), e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public DefaultWXPayConfig getConfig(){
        return this.config;
    }

    public void setP12File(P12File p12File) {
        this.p12File = p12File;
    }

    public void setPrivateKeyContent(String privateKeyContent) {
        this.privateKeyContent = privateKeyContent;
    }

    public String buildXCXData(String keys)  {
        try {
            Long timestamp = WXPayUtils.buildTimestamp();
            String nonceStr = WXPayUtils.buildNonceStr();
            String packageData = "prepay_id=" + keys;
            PrivateKey privateKey = WXPayUtils.readPrivateKey(privateKeyContent);
            String message = config.getAppId() + "\n" + timestamp + "\n" + nonceStr + "\n" + packageData + "\n";
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey);
            sign.update(message.getBytes("utf-8"));
            String signature = Base64.getEncoder().encodeToString(sign.sign());

            Map<String, Object> data = new LinkedHashMap<>();
            data.put("appId", config.getAppId());
            data.put("timeStamp", timestamp);
            data.put("nonceStr", nonceStr);
            data.put("package", packageData);
            data.put("signType", "RSA");
            data.put("sign", signature);
            return JsonTools.toJson(data);
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }
        return null;
    }

    public PayJSConfig buildJSConfig(String prepayId){
        try {
            PayJSConfig jsConfig = new PayJSConfig();
            jsConfig.setAppId(config.getAppId());
            jsConfig.setTimeStamp(WXPayUtils.buildTimestamp().toString());
            jsConfig.setNonceStr(WXPayUtils.buildNonceStr());
            jsConfig.setPrepay_id(prepayId);
            jsConfig.setSignType("RSA");

            String packageData = "prepay_id="+jsConfig.getPrepay_id();

            PrivateKey privateKey = WXPayUtils.readPrivateKey(privateKeyContent);
            String message = jsConfig.getAppId() + "\n" + jsConfig.getTimeStamp() + "\n" + jsConfig.getNonceStr() + "\n" + packageData + "\n";
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey);
            sign.update(message.getBytes("utf-8"));
            String signature = Base64.getEncoder().encodeToString(sign.sign());

            jsConfig.setPaySign(signature);
            return jsConfig;
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }
        return null;
    }
}
